/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 16.2:

Networking 3: SC and the Internet

Contents:

• Networking computers through the Internet

- Connecting

- A simple example

- Example with sound, and some feedback 

• Sending audio through the internet

• Capturing MP3 streams

- Reading online streams

• Internet radio alarm clock

• More internet related quarks

==========================================================

*/



// ================= SUPERCOLLIDER AND THE INTERNET =================



// ====== NETWORKING COMPUTERS THROUGH THE INTERNET ======


// Unfortunately, connecting to remote machines through the internet is not as simple as through LAN networks. You need a server hub to which all the machines will connect, and which will route the messages accordingly. Once you have the server set up, all you need to do is connect to it using its IP address or a named DSN server, and logging into a specific group that is the same for all machines, with a unique (per machine) id.


// You also need a program to handle the communication with the server. Josh Parmenter has built a class that interfaces with Ross Bencina's  OscGroups UNIX application, available here:

"open http://www.audiomulch.com/~rossb/code/oscgroups/".unixCmd

// This contains two applications OscGroupClient and OscGroupServer; you should add them both to SuperCollider's main directory.

// Then, get the Quark:

Quarks.install( "OscGroupClient", checkoutIfNeeded: false)

// You will also need the ProcessTools quark to communicate with these UNIX applications from SC:

Quarks.install( "ProcessTools", checkoutIfNeeded: false)


// Luckily, JP has also setup a server for you to use, so this is actually possible to try out!


// ------ Connecting --

// Once you've installed these UNIX applications, you can try the following



// • Step 1: 

// Create a new OscGroupClient object

// The info below is using that server to handle the communication:

                  // server address       ,userid,   userpassword, groupid, grouppassword

a = OscGroupClient("realizedsound.mooo.com", "me", "meword", "490a", "490aword")

// Login using a different userid/pass with another machine; ex:

// a = OscGroupClient("realizedsound.mooo.com", "myself", "myselfword", "490a", "490aword")


// • Step 2:  

// Join the Server

a.join;

 


// • Step 3:  

// Add a responder for OTHER people on the server to send messages to you

a.addResp(\test, {arg time, resp, msg;

msg.postln;

});


// • Step 4:

// Send some messages  

// this sends a message to everyone else connected, and with the appropriately named responder activated. You don't see anything though on your end

a.sendMsg(\test)


// you can send more then just the trigger id, just comma separate the stuff you are sending!):

a.sendMsg(\test"How much can I send?", 1232, \etc);



// and remove your responder. This is an IdentityDictionary in the class, so you can have as 

// many responders with unique ids that you want

a.removeResp(\test);



// ATTENTION: after you're done, don't forget to close your connection to the server! 

a.close;



// ------ A simple example --


// In this simple example, we will create a GUI interface with two knobs, one to send data to a remote machine, and one to receive. The same code will work on the remote machine(s), only make sure you use a different <userid,   userpassword>!


// 1. connecting:

                  // server address       ,userid,   userpassword, groupid, grouppassword

~serverConnx = OscGroupClient("realizedsound.mooo.com", "me", "meword", "490a", "490aword"); 

// ATTN:   with another machine using a different userid/pass; ex:

// ~serverConnx = OscGroupClient("realizedsound.mooo.com", "myself", "myselfword", "490a", "490aword")


// join the Server

~serverConnx.join;


// 2. making a responder for receiving

~serverConnx.addResp(\test, {arg time, resp, msg;

[time, msg].postln;

{~recKnob.value_(msg[1])}.defer;

});


// 3. Making a GUI window with a knob to send, and a knob to receive:

(

var window = Window.new("Networking test",Rect(318, 456, 400, 400)).front;

~recKnob = Knob.new(window,Rect(175, 205, 217, 189))

.action_{|v| };

~sendKnob = Knob.new(window,Rect(8, 11, 216, 188))

.action_{|v| 

~serverConnx.sendMsg('test', v.value); // << action: sending to remote machine

};

StaticText.new(window,Rect(24, 333, 148, 50))

.string_("Receive some data -->>")

.action_{|v| };

StaticText.new(window,Rect(229, 15, 130, 47))

.string_("<<-- Send some data")

.action_{|v| };

)




// Don't forget to close your connecton to the server once you're done!

~serverConnx.close




// ------ Example with sound, and some feedback --


// In this simple example, we will create the same GUI interface with two knobs, except this time the 'sending' knob will get data from analysis of the sound in the local machine, and will send the data to the remote machine to affect the synthesis there - and vice versa.

// The same code will work on the remote machine(s), only make sure you use a different <userid,   userpassword>!


// • Synthesis: a self-phase-modulated Sinewave, with analysis to show how noisy it is:


(

~mult = 4;

// buffer for FFT 

~buf = Buffer.alloc(s,2048,1); 

// the synthdef

~fdbFM = CtkSynthDef(\fdbSine, {arg freq, fdbAmt, amp, pollFreq = 2, gate = 1;

var sine, mod, fdbIn, fdbOut, env, chain, flatness;

fdbIn = LocalIn.ar(1);

env = EnvGen.kr(Env.new([0.00001, 1, 1, 0.00001], [0.05, 0.9, 0.5], [\exp, \sin], 1), gate,  doneAction: 2);

mod = fdbIn * fdbAmt;

sine = SinOsc.ar(freq, mod, amp); // fdb phase modulation

chain = FFT(~buf, sine);

flatness = SpecFlatness.kr(chain);

SendReply.kr(Impulse.kr(pollFreq), 'flatness', flatness);

Out.ar(0, sine * env);

fdbOut = LocalOut.ar(sine);

})

);



// 1. connecting:

                  // server address       ,userid,   userpassword, groupid, grouppassword

~serverConnx = OscGroupClient("realizedsound.mooo.com", "me", "meword", "490a", "490aword"); 

// ATTN: Login  with another machine using a different userid/pass; ex:

// ~serverConnx = OscGroupClient("realizedsound.mooo.com", "myself", "myselfword", "490a", "490aword")


// join the Server

~serverConnx.join;


// 2a. making a responder for receiving

~serverConnx.addResp(\test, {arg time, resp, msg;

[time, msg].postln;

{~recKnob.value_(msg[1])}.defer; // show what's coming in

~note.fdbAmt_(msg[1] * ~mult + 1)

});

// 2a. making a responder for receiving from the analysis data from the synthdef, and moving the knob

~noisiness = OSCresponder(n, 'flatness', { arg time, resp, msg; 

msg[3].postln;

{~sendKnob.valueAction_(msg[3])}.defer; // naturally, it would be more efficient to send the message directly to the remote server, this is just for demonstration purposes...

}).add;



// 3. Making a GUI window with a knob to send, and a knob to receive:

(

var window = Window.new("Networking test",Rect(318, 456, 400, 400)).front;

~recKnob = Knob.new(window,Rect(175, 205, 217, 189))

.action_{|v| };

~sendKnob = Knob.new(window,Rect(8, 11, 216, 188))

.action_{|v| 

~serverConnx.sendMsg('test', v.value); // << action: sending to remote machine

};

StaticText.new(window,Rect(24, 333, 148, 50))

.string_("Receive some data -->>");

StaticText.new(window,Rect(229, 15, 130, 47))

.string_("<<-- Send some data");

)




~note = ~fdbFM.new().freq_(160).fdbAmt_(3.5).amp_(0.5).play

// change some numbers

~note.freq_(124)

~note.fdbAmt_(5.4)

~note.fdbAmt_(1244)

~mult = 4.6

~note.pollFreq_(2.6)

~note.release




~noisiness.remove; // remove the responder

~serverConnx.close; // close the server connection





// ====== SENDING AUDIO THROUGH THE INTERNET ======


// You can only send data with OscGroupClient. Audio would require massive amounts of bandwidth, not really possible in real-time with household connections - though possible at some universities.

// CCRMA has developed a fast protocol for sending multiple, high-quality audio channels, called JackTrip. Check it out:

 ccrma.stanford.edu—soundwire

 code.google.com—jacktrip

 

// A more bandwidth-affordable method would be using something like icecast or nicecast, for streaming audio as mp3. However, as these require an encoding/decoding process, there is also a lot of latency built in. 

www.icecast.org


// Surprisingly enough, the best solution is using something like skype, and routing audio to/from it through Jack







// ====== CAPTURING MP3 STREAMS ======



// MP3 is a class that acts as an SC intrface for the curl (downloading utility) and lame (mp3 encoder/decoder) command line tools, allowing you to playback .mp3 and .ogg files and stream audio within SC. It works on Max OS X and Linux.



// First, check if lame and curl are installed and where:

MP3.lamepath;

MP3.curlpath;

File.exists(MP3.lamepath)

File.exists(MP3.curlpath)


// This class has 3 different modes: reading files locally (\readfile), online (\readurl), or writing files locally (\writefile).

// If you want to stream audio online, you will need to use something like icecast:

"open http://en.flossmanuals.net/icecast".unixCmd




s.boot


// Something you need to beware of: some streams are mono, others are stereo. Just make sure you use the appropriate synthdef and buffer for a stream:

// SynthDefs:

(

~mp3Stereo = CtkSynthDef("mp3StreamSter", { |outbus = 0, bufnum = 0, amp = 1|

var stream, chanAmt;

chanAmt = 2;

stream = DiskIn.ar(chanAmt, bufnum);

Out.ar(0, Pan2.ar(stream, 0, amp));

});


~mp3Mono = CtkSynthDef("mp3StreamMon", { |outbus = 0, bufnum = 0, amp = 1|

var stream, chanAmt;

chanAmt = 1;

stream = DiskIn.ar(chanAmt, bufnum);

Out.ar(0, Pan2.ar(stream, 0, amp));

});

)



// ------ Reading online streams --


// Choose a stream

// Seattle's KEXP:

~stream = MP3("http://kexp-mp3-2.cac.washington.edu:8000/", \readurl);

// Free Underground Tekno

~stream = MP3("http://ch1.freeundergroundtekno.org:8018/", \readurl);

// many more streams here:

"open http://www.streamfinder.com/".unixCmd



// Some streames don't point you to the address of the stream, but use a playlist file instead (ex: .pls, .m3u). To get the address where these playlist files point at do:

"curl http://ch1.freeundergroundtekno.org:8018/listen.pls".unixCmdGetStdOut

// start the stream:

~stream.start;


// Allocate the correct buffer for DiskIn to use:

~bufSter = Buffer.cueSoundFile(s, ~stream.fifo, 0, 2);

// OR:

//~bufMon = Buffer.cueSoundFile(s, ~stream.fifo, 0, 2);


// start the synth

~mp3player = ~mp3Stereo.new().bufnum_(~bufMon).amp_(1).play;

// OR:

//~mp3player = ~mp3Mono.new().bufnum_(~bufSter).amp_(1).play;



// ATTENTION: don't forget to clean up! MP3 creates a new instance of the Unix apps and if you don't turn them off from SC they 'll keep being active, and possibly freeze up your servers!

~mp3player.free

~bufSter.close.free;

~bufMon.close.free;

~stream.finish





// ====== INTERNET RADIO ALARM CLOCK ======


// Here 's a fun application: use SC as an alarm clock to wake up to an internet radio station! 


// First download the DayTimer quark, a class that lets you do specific things in specific times of a year/month/day/hour/minute

Quarks.install( "DayTimer", checkoutIfNeeded: false)


// Alarm clock:

~alarm = DayTimer(\alarmClock);

~alarm.start; // start watching for dates


// do once a day at a specific time [h, m, s]:

(

~alarm.putDate(\wakeUpTime, [22, 28, 10], {

~mp3player = ~mp3.new().bufnum_(~buf).amp_(1).play } ); // start the stream at that specific time

)


~alarm.stop; // stop watching for dates








// ====== MORE QUARKS ======

// You may also want to get the FreeSound quark, for automatically quering and downloading files from the FreeSound database:

Quarks.install( "FreeSound", checkoutIfNeeded: false)